﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;


namespace Present.Commands
{
    public interface IPresentVisualStateManager
    {
        bool ChangeState(FrameworkElement ctrl, string state, Action onCompleted);
    }

    /// <summary>
    /// Provides extra functionality to the normal statemanager
    /// Notably, the ability to "bind" state changes to a property representing the desired state.
    /// The ability to run code after a state transition has finished.
    /// </summary>
    public class PresentVisualStateManager : VisualStateManager, IPresentVisualStateManager
    {
        public static string GetGoToState(DependencyObject obj)
        {
            return (string)obj.GetValue(GoToStateProperty);
        }

        public static void SetGoToState(DependencyObject obj, string value)
        {
            obj.SetValue(GoToStateProperty, value);
        }

        public static readonly DependencyProperty GoToStateProperty =
            DependencyProperty.RegisterAttached(
                "GoToState",
                typeof(string),
                typeof(PresentVisualStateManager),
                new PropertyMetadata((s, e) =>
                                         {
                                             var ctrl = s as FrameworkElement;
                                             if (ctrl == null)
                                                 throw new InvalidOperationException("This attached property only supports types derived from Control.");


                                             ChangeState(ctrl, (string)e.NewValue, ()=>{});
                                         }));

        public static bool ChangeState(FrameworkElement ctrl, string state, Action onCompleted)
        {

            SubscribeToStateCompletion(ctrl, state, onCompleted);
            
            if (!GoToState(ctrl, state, true))
            {
                if (!GoToElementState(ctrl, state, true))
                {
                    Debug.WriteLine("Unable to Tranistion to State " + state);
                    return false;
                }
            }
            return true;
            
        }



        private static void SubscribeToStateCompletion(FrameworkElement ctrl, string stateName, Action onCompleted)
        {


            var state = TryGetState(stateName, ctrl);
            if (state == null || state.Storyboard == null)
                return;

            var onCompletedObservable = Observable.FromEventPattern(e => state.Storyboard.Completed += e, e => state.Storyboard.Completed -= e);
            onCompletedObservable.Take(1).Subscribe(x => onCompleted());
        }

        public static VisualState TryGetState(string stateName, FrameworkElement ctrl)
        {
            var groups = GetVisualStateGroups(ctrl);
            if (groups.IsEmpty())
            {
                var templateControl = ctrl as Control;
                if(templateControl != null)
                    groups = GetVisualStateGroups(templateControl.GetType()
                        .GetProperty("TemplateChild", BindingFlags.Instance | BindingFlags.NonPublic)
                        .GetValue(templateControl, null) as FrameworkElement);
            }
            return groups.OfType<VisualStateGroup>().SelectMany(g => g.States.OfType<VisualState>()).FirstOrDefault(s => s.Name== stateName);
        }


        bool IPresentVisualStateManager.ChangeState(FrameworkElement ctrl, string state, Action onCompleted)
        {
            return ChangeState(ctrl, state, onCompleted);
        }
    }
}